﻿using System.Collections.ObjectModel;
using System;
using System.Linq;
using WindowsInput.Native;
using System.IO;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json;

namespace UniPie
{
	/// <summary>
	/// This is the main storage of all the behaviors UniPie handles.
	/// </summary>
	public class Data
	{
		/// <summary>
		/// The file name of file where we store data between sessions. 
		/// </summary>
		private static string DataFileName = "UniPie_data.json";

		/// <summary>
		/// The path where the data file should be placed. 
		/// </summary>
		private static string DataFilePath = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments) + "\\" + DataFileName;

		private static ObservableCollection<Behavior> _behaviors = new ObservableCollection<Behavior>();
		public static ObservableCollection<Behavior> Behaviors
		{
			get { return _behaviors; }
		}

		/// <summary>
		/// The constructor.
		/// </summary>
		static Data()
		{
			LoadData();
		}

		/// <summary>
		/// Loads data from file.
		/// </summary>
		private static void LoadData()
		{
			FileStream DataStream;
			string StringData;
			JArray JsonBehaviors;
			JArray JsonSliceDefinitions;
			JArray JsonKeyMouseCombo;
			JArray JsonArea;
			byte[] buffer;

			if (File.Exists(DataFilePath))
			{
				DataStream = new FileStream(DataFilePath, FileMode.Open, FileAccess.Read);
			}
			else
			{
				_behaviors.Add(new Behavior());

				// HACK: This 17 is the total number of slices in a pie menu. This should be a single constant
				// defined somewhere else.
				for (int j = 0; j < 17; j++)
				{
					_behaviors[0].Response.SliceDefinitions.Add(new SliceDefinition());
				}
				return;
			}

			try
			{
				int length = (int)DataStream.Length;  // get file length
				buffer = new byte[length];            // create buffer
				int count;                            // actual number of bytes read
				int sum = 0;                          // total number of bytes read

				// Read until Read method returns 0 (end of the stream has been reached)
				while ((count = DataStream.Read(buffer, sum, length - sum)) > 0)
					sum += count;  // sum is a buffer offset for next reading
			}
			finally
			{
				DataStream.Close();
			}

			StringData = System.Text.Encoding.Default.GetString(buffer);
			try
			{
				JsonBehaviors = JArray.Parse(StringData);
			}
			catch (Newtonsoft.Json.JsonReaderException e)
			{
				Log.w("JsonReaderException: " + e.ToString());
				return;
			}

			// Here we manually assign JSON fields to variables. This aproach will make it easier
			// to read data files which have a different Data version than the running application.
			// So later on we'll have different variations of this block for different Data versions.
			for (int i = 0; i < JsonBehaviors.Count; i++)
			{
				_behaviors.Add(new Behavior());
				_behaviors[i].Name = (string)JsonBehaviors[i]["Name"];
				_behaviors[i].TriggerState.ApplicationFilter.ApplicationClass = (string)JsonBehaviors[i]["TriggerState"]["ApplicationFilter"]["ApplicationClass"];
				_behaviors[i].TriggerState.ApplicationFilter.WindowCaption = (string)JsonBehaviors[i]["TriggerState"]["ApplicationFilter"]["WindowCaption"];
				_behaviors[i].TriggerState.ApplicationFilter.IsSystemWide = (bool)JsonBehaviors[i]["TriggerState"]["ApplicationFilter"]["IsSystemWide"];

				JsonKeyMouseCombo = (JArray)JsonBehaviors[i]["TriggerState"]["KeyboardMouseCombo"];
				for (int k = 0; k < JsonKeyMouseCombo.Count; k++)
				{
					_behaviors[i].TriggerState.KeyboardMouseCombo.Add((VirtualKeyCode)(int)JsonKeyMouseCombo[k]);
				}

				_behaviors[i].TriggerState.ScreenArea.DesktopIndex = (int)JsonBehaviors[i]["TriggerState"]["ScreenArea"]["DesktopIndex"];
				_behaviors[i].TriggerState.ScreenArea.EdgeWidth = (int)JsonBehaviors[i]["TriggerState"]["ScreenArea"]["EdgeWidth"];
				JsonArea = (JArray)JsonBehaviors[i]["TriggerState"]["ScreenArea"]["Area"];
				for (int k = 0; k < JsonArea.Count; k++)
				{
					_behaviors[i].TriggerState.ScreenArea.Area[k] = (bool)JsonArea[k];
				}

				_behaviors[i].Response.UseRemapping = (bool)JsonBehaviors[i]["Response"]["UseRemapping"];
				JsonKeyMouseCombo = (JArray)JsonBehaviors[i]["Response"]["RemappedCombo"];
				for (int k = 0; k < JsonKeyMouseCombo.Count; k++)
				{
					_behaviors[i].Response.RemappedCombo.Add((VirtualKeyCode)(int)JsonKeyMouseCombo[k]);
				}
				JsonSliceDefinitions = (JArray)JsonBehaviors[i]["Response"]["SliceDefinitions"];
				for (int j = 0; j < JsonSliceDefinitions.Count; j++)
				{
					_behaviors[i].Response.SliceDefinitions.Add(new SliceDefinition());
					_behaviors[i].Response.SliceDefinitions[j].Hue = (double)JsonSliceDefinitions[j]["Hue"];
					_behaviors[i].Response.SliceDefinitions[j].Saturation = (double)JsonSliceDefinitions[j]["Saturation"];
					_behaviors[i].Response.SliceDefinitions[j].IsEnabled = (bool)JsonSliceDefinitions[j]["IsEnabled"];
					_behaviors[i].Response.SliceDefinitions[j].Text = (string)JsonSliceDefinitions[j]["Text"];
					JsonKeyMouseCombo = (JArray)JsonSliceDefinitions[j]["ComboToSend"];
					for (int k = 0; k < JsonKeyMouseCombo.Count; k++)
					{
						_behaviors[i].Response.SliceDefinitions[j].ComboToSend.Add((VirtualKeyCode)(int)JsonKeyMouseCombo[k]);
					}
				}

			}
		}


		/// <summary>
		/// Saves data to file.
		/// </summary>
		public static void SaveData()
		{
			FileStream DataStream;
			StreamWriter DataStreamWriter;

			try { DataStream = new FileStream(DataFilePath, FileMode.Create, FileAccess.Write); }
			catch (IOException e)
			{
				Log.i("IOException (Open/Create): " + e.ToString());
				return;
			}
			DataStream.Close();

			try
			{
				using (DataStreamWriter = new StreamWriter(DataFilePath))
				{
					DataStreamWriter.Write(JsonConvert.SerializeObject(_behaviors));
				}
			}
			catch (IOException e)
			{
				Log.i("IOException (Write): " + e.ToString());
				DataStream.Close();
				return;
			}

			Log.i("Settings saved.");
			DataStream.Close();
		}

		/// <summary>
		/// Generates random data for testing purposes.
		/// </summary>
		private static void GenerateData()
		{
			Random rnd = new Random();
			for (int i = 0; i < 8; i++)
			{
				_behaviors.Add(new Behavior());
				_behaviors[i].Name = "Behavior-" + i;

				if (i == 0)
				{
					_behaviors[i].TriggerState.KeyboardMouseCombo.Add(VirtualKeyCode.LCONTROL);
					_behaviors[i].TriggerState.KeyboardMouseCombo.Add(VirtualKeyCode.MBUTTON);
				}
				else if (i == 1)
				{
					_behaviors[i].TriggerState.KeyboardMouseCombo.Add(VirtualKeyCode.LSHIFT);
					_behaviors[i].TriggerState.KeyboardMouseCombo.Add(VirtualKeyCode.RBUTTON);
				}

				_behaviors[i].TriggerState.ApplicationFilter.WindowCaption = "Caption-" + Math.Floor(rnd.NextDouble() * 100);
				_behaviors[i].TriggerState.ApplicationFilter.ApplicationClass = "Class-" + Math.Floor(rnd.NextDouble() * 100);
				for (int j = 0; j < 9; j++)
				{
					_behaviors[i].TriggerState.ScreenArea.Area[j] = rnd.NextDouble() > 0.5;
				}
				_behaviors[i].Response.UseRemapping = rnd.NextDouble() > 0.5;
				for (int j = 0; j < 17; j++)
				{
					_behaviors[i].Response.SliceDefinitions[j].IsEnabled = rnd.NextDouble() > 0.25;
					if (j == 0) { _behaviors[i].Response.SliceDefinitions[j].IsEnabled = true; }
					else { _behaviors[i].Response.SliceDefinitions[j].IsEnabled = j == i + 1; }
					_behaviors[i].Response.SliceDefinitions[j].Text = "Test slice " + ((i * 100) + j);
					_behaviors[i].Response.SliceDefinitions[j].Hue = ((double)i / 8);
					_behaviors[i].Response.SliceDefinitions[j].Saturation = ((double)j / 16);
					_behaviors[i].Response.SliceDefinitions[j].ComboToSend.Add(VirtualKeyCode.RBUTTON);
				}
			}
		}

		/// <summary>
		/// Inserts a new behavior at a given index. Invoked from the UI.
		/// </summary>
		public static void AddBehavior(int index)
		{
			Behavior b = new Behavior();

			for (int j = 0; j < 17; j++)
			{
				b.Response.SliceDefinitions.Add(new SliceDefinition());
			}
			_behaviors.Insert(index, b);
		}

		/// <summary>
		/// Deletes behavior at a given index. Invoked from the UI.
		/// </summary>
		public static void DeleteBehavior(int index)
		{
			_behaviors.RemoveAt(index);
		}

		/// <summary>
		/// Sorts behaviors.
		/// </summary>
		public static void DuplicateBehavior(int index)
		{
			Behavior b = new Behavior(_behaviors[index]);

			b.Name = b.Name + "Copy";
			_behaviors.Insert(index + 1, b);
		}

		/// <summary>
		/// Sorts behaviors.
		/// </summary>
		public static void SortBehaviors()
		{
			Behavior[] barray = _behaviors.ToArray();

			Array.Sort(barray, delegate(Behavior b1, Behavior b2) { return b1.Name.CompareTo(b2.Name); });

			_behaviors.Clear();
			foreach (Behavior b in barray)
			{
				_behaviors.Add(b);
			}
		}
	}
}
